7. 도커 개요5 (레이어 구조 실습)
실습 개요
실습 진행 전 안내:
이 실습은 본인의 실습 디렉토리에서 진행
- Windows:
C:\docker-practice또는 원하는 경로- Linux/Mac:
~/docker-practice또는 원하는 경로아래 예시 경로는 참고용이며, 본인 환경에 맞게 수정하여 진행
⚠️ Windows 사용자 :
C 드라이브 사용 권장 - Docker Desktop은 기본적으로 C 드라이브에 대한 파일 공유 권한을 가지고 있음
다른 드라이브 사용 시 권한 설정 필요:
- Docker Desktop 설정 → Resources → File Sharing
- 사용할 드라이브 또는 디렉토리 추가
- Apply & Restart 클릭
권한 문제 증상:
docker build시 "cannot find the path" 에러COPY명령 실패- 파일 접근 거부 오류
권장 사항: 실습 중 권한 문제가 발생하면
C:\docker-practice로 변경
이 문서에서는 컨테이너 레이어 구조를 직접 확인하는 실습을 진행. 총 3개의 실습으로 구성:
실습 구성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[실습 준비] 이미지 확인 및 빌드
↓
[실습 1] docker save로 이미지 내부 분석
↓
[실습 2] 오버레이 파일시스템 직접 체험
↓
[실습 3] Docker 스토리지 위치 확인 (선택)
실습 환경:
- Windows: PowerShell + Docker Desktop
- Linux/Mac: Bash + Docker
- WSL2: Linux 기능 필요 시 사용 (실습 2, 3)
실습 준비: myimage:v1 이미지 확인 및 빌드
1단계: 이미지 존재 여부 확인
모든 OS 공통 (PowerShell/Bash):
# 이미지 목록 확인
docker images myimage
# 출력 예시 1 - 이미지가 있는 경우:
# REPOSITORY TAG IMAGE ID CREATED SIZE
# myimage v1 a1b2c3d4e5f6 2 hours ago 77.8MB
# 출력 예시 2 - 이미지가 없는 경우:
# REPOSITORY TAG IMAGE ID CREATED SIZE
결과 판단:
- 이미지가 있으면 → 2단계 생략하고 바로 실습 1로 이동
- 이미지가 없으면 → 2단계 진행
2단계: myimage:v1 이미지 빌드 (이미지가 없는 경우)
2-1. 실습 디렉터리로 이동 및 확인
PowerShell (Windows):
# 실습 디렉터리로 이동 (본인 경로로 변경)
cd C:\docker-practice
# hello 디렉터리 확인 (이미 있는지 확인)
ls hello
# 출력 예시:
# 디렉터리: C:\docker-practice\hello
#
# Mode LastWriteTime Length Name
# ---- ------------- ------ ----
# -a---- 2026-01-08 오전 10:30 100 hello.sh
# -a---- 2026-01-08 오전 10:30 150 Dockerfile
Bash (Linux/Mac):
# 실습 디렉터리로 이동 (본인 경로로 변경)
cd ~/docker-practice
# hello 디렉터리 확인 (이미 있는지 확인)
ls -la hello/
# 출력 예시:
# total 8
# -rw-r--r-- 1 user user 100 Jan 8 10:30 hello.sh
# -rw-r--r-- 1 user user 150 Jan 8 10:30 Dockerfile
만약 hello 디렉터리가 없다면 새로 생성:
PowerShell (Windows):
# hello 디렉터리 생성
mkdir hello
cd hello
# hello.sh 스크립트 작성
@"
#!/bin/bash
set -eu
echo "Hello, World!"
exec sleep infinity
"@ | Set-Content hello.sh -NoNewline
# Dockerfile 작성
@"
FROM ubuntu:22.04
COPY ./hello.sh /hello.sh
RUN chmod +x /hello.sh
ENTRYPOINT ["/hello.sh"]
"@ | Set-Content Dockerfile -NoNewline
# 상위 디렉터리로 이동
cd ..
Bash (Linux/Mac):
# hello 디렉터리 생성
mkdir -p hello
cd hello
# hello.sh 스크립트 작성
cat > hello.sh << 'EOF'
#!/bin/bash
set -eu
echo "Hello, World!"
exec sleep infinity
EOF
# Dockerfile 작성
cat > Dockerfile << 'EOF'
FROM ubuntu:22.04
COPY ./hello.sh /hello.sh
RUN chmod +x /hello.sh
ENTRYPOINT ["/hello.sh"]
EOF
# 상위 디렉터리로 이동
cd ..
2-2. 이미지 빌드 실행
모든 OS 공통 (PowerShell/Bash):
# hello 디렉터리를 컨텍스트로 이미지 빌드
docker build -t myimage:v1 ./hello
빌드 과정 출력 예시:
[+] Building 12.3s (8/8) FINISHED
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 150B 0.0s
=> [internal] load .dockerignore 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 1.2s
=> [1/3] FROM docker.io/library/ubuntu:22.04@sha256:... 5.4s
=> => resolve docker.io/library/ubuntu:22.04@sha256:... 0.0s
=> => sha256:... 29.53MB / 29.53MB 4.2s
=> => extracting sha256:... 1.1s
=> [internal] load build context 0.1s
=> => transferring context: 100B 0.0s
=> [2/3] COPY ./hello.sh /hello.sh 0.2s
=> [3/3] RUN chmod +x /hello.sh 0.8s
=> exporting to image 0.3s
=> => exporting layers 0.2s
=> => writing image sha256:a1b2c3d4e5f6... 0.0s
=> => naming to docker.io/library/myimage:v1 0.0s
2-3. 빌드 완료 확인
모든 OS 공통 (PowerShell/Bash):
# 이미지 생성 확인
docker images myimage
# 출력:
# REPOSITORY TAG IMAGE ID CREATED SIZE
# myimage v1 a1b2c3d4e5f6 10 seconds ago 77.8MB
실습 1: 컨테이너 이미지 내부 분석
실습 목표
docker save 명령어로 myimage:v1 이미지를 tar 형식으로 추출하고 내부 파일 구조를 분석하여 레이어 구조를 확인
docker save: 이미지를 tar 파일로 추출하는 명령어. 레이어 구조 분석, 백업, 오프라인 전송에 사용됨.
tar: Tape Archive의 약자. 여러 파일/디렉터리를 하나로 묶는 아카이브 형식.
실습 환경
- 도구: PowerShell (Windows) 또는 Bash (Linux/Mac)
- 위치: 본인의 실습 디렉터리
1단계: 이미지 추출용 디렉터리 생성
PowerShell (Windows):
# 실습 디렉터리로 이동 (본인 경로로 변경)
cd C:\docker-practice
# 이미지 추출용 디렉터리 생성
mkdir dumpimage
# 결과 확인
ls dumpimage
# 출력: (빈 디렉터리)
Bash (Linux/Mac):
# 실습 디렉터리로 이동 (본인 경로로 변경)
cd ~/docker-practice
# 이미지 추출용 디렉터리 생성
mkdir -p dumpimage
# 결과 확인
ls -la dumpimage/
# 출력: (빈 디렉터리)
2단계: docker save로 이미지 추출
PowerShell (Windows):
⚠️ 중요: Windows PowerShell에서
docker save | tar형태의 파이프는 바이너리 데이터 손상 문제가 발생. 반드시 중간 파일을 생성하는 방식을 사용
# ❌ 작동하지 않음 (PowerShell 파이프 문제)
# docker save myimage:v1 | tar -xC ./dumpimage
# ✅ 올바른 방법: 중간 파일 생성
# 1. 이미지를 tar 파일로 저장
docker save myimage:v1 -o myimage.tar
# 2. tar 파일 압축 해제
tar -xf myimage.tar -C ./dumpimage
# 3. 임시 tar 파일 삭제 (선택)
rm myimage.tar
Bash (Linux/Mac):
# 방법 1: 파이프 사용 (권장)
docker save myimage:v1 | tar -xC ./dumpimage
# 방법 2: 중간 파일 생성
docker save myimage:v1 -o myimage.tar
tar -xf myimage.tar -C ./dumpimage
rm myimage.tar
처리 시간: 약 2-5초 (이미지 크기에 따라 다름)
3단계: 추출된 파일 구조 확인
PowerShell (Windows):
# 디렉터리 구조 확인
ls ./dumpimage/
출력 (OCI 형식):
디렉터리: C:\docker-practice\dumpimage
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2026-01-05 오후 10:14 blobs
-a---- 2026-01-08 오후 11:24 355 index.json
-a---- 1970-01-01 오전 9:00 1371 manifest.json
-a---- 1970-01-01 오전 9:00 31 oci-layout
-a---- 1970-01-01 오전 9:00 86 repositories
Bash (Linux/Mac):
# 디렉터리 구조 확인
ls -la ./dumpimage/
출력 (OCI 형식):
total 24
drwxr-xr-x 3 user user 4096 Jan 8 23:24 .
drwxr-xr-x 5 user user 4096 Jan 8 23:20 ..
drwxr-xr-x 3 user user 4096 Jan 5 22:14 blobs
-rw-r--r-- 1 user user 355 Jan 8 23:24 index.json
-rw-r--r-- 1 user user 1371 Jan 1 1970 manifest.json
-rw-r--r-- 1 user user 31 Jan 1 1970 oci-layout
-rw-r--r-- 1 user user 86 Jan 1 1970 repositories
📌 참고: OCI vs Moby 형식
최신 Docker는 OCI(Open Container Initiative) 형식으로 저장하며, 레이어 파일들이 blobs/sha256/ 디렉터리에 저장. 구조는 다르지만 레이어 개념은 동일
OCI (Open Container Initiative): 컨테이너 표준을 정의하는 단체. 이미지 형식, 런타임 규격 표준화를 담당함.
SHA256: 256비트 해시 함수. 레이어, 이미지 등을 고유하게 식별하는 데 사용됨.
파일 설명:
| 파일/디렉터리 | 역할 | 내용 |
|---|---|---|
blobs/sha256/ |
레이어 저장소 | 모든 레이어 tar 파일이 SHA256 해시명으로 저장됨 |
manifest.json |
이미지 구성 정보 | 레이어 순서, Config 파일 위치, 태그 |
index.json |
OCI 인덱스 | manifest 위치 정보 |
repositories |
태그 정보 | 이미지 이름 및 태그 |
oci-layout |
OCI 규격 버전 | 이미지 포맷 버전 |
4단계: manifest.json 내용 확인
# manifest.json 읽기
cat ./dumpimage/manifest.json | ConvertFrom-Json | ConvertTo-Json -Depth 5
출력 예시 (간소화):
[
{
"Config": "2c3d4e5f6a7b8901...json",
"RepoTags": [
"myimage:v1"
],
"Layers": [
"0a1b2c3d4e5f6789.../layer.tar",
"1b2c3d4e5f6a7890.../layer.tar",
"2c3d4e5f6a7b8901.../layer.tar"
]
}
]
분석:
Config: 이미지 최종 설정 파일 (ENTRYPOINT, ENV 등)RepoTags: 이미지 이름 및 태그 (myimage:v1)Layers: 레이어 tar 파일 목록 (순서대로 적용됨)
5단계: 특정 레이어 내용 확인
5-1. layer.tar 파일 목록 확인
# 레이어 2 (hello.sh 추가 레이어) 확인 - 실제 해시값 사용
# 예시: 1b2c3d4e5f6a7890으로 시작하는 디렉터리 찾기
ls ./dumpimage/1b2c3d4e5f6a7890*/
# layer.tar 내부 파일 목록 확인 (처음 10개만)
tar --list -f ./dumpimage/1b2c3d4e5f6a7890*/layer.tar | Select-Object -First 10
출력 예시:
hello.sh
분석:
- 레이어 2는
hello.sh파일만 추가함 - 이는 Dockerfile의
COPY ./hello.sh /hello.sh명령 결과
5-2. layer.tar에서 hello.sh 파일 추출 및 내용 확인
# hello.sh 파일 내용 직접 출력 (압축 해제 없이)
tar -xOf ./dumpimage/1b2c3d4e5f6a7890*/layer.tar hello.sh
출력 예시:
#!/bin/bash
set -eu
echo "Hello, World!"
exec sleep infinity
분석:
- 이전에 작성한
hello.sh스크립트가 레이어에 정확히 포함됨
5-3. 레이어 3 (chmod 실행) 확인
PowerShell (Windows):
# 레이어 3의 layer.tar 내용 확인
tar --list -f ./dumpimage/2c3d4e5f6a7b8901*/layer.tar
Bash (Linux/Mac):
# 레이어 3의 layer.tar 내용 확인
tar --list -f ./dumpimage/2c3d4e5f6a7b8901*/layer.tar
출력 예시:
hello.sh
분석:
chmod +x /hello.sh명령은 파일 권한을 변경함- 이는 파일 메타데이터(권한) 변경이므로 layer.tar에 다시 기록됨
- CoW 방식: 기존
hello.sh를 복사 후 권한 변경
6단계: 레이어 구조 시각화
위 분석 결과를 바탕으로 myimage:v1의 레이어 구조를 정리:
myimage:v1 레이어 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[레이어 3] 2c3d4e5f6a7b8901.../
│ layer.tar
│ └─ hello.sh (실행 권한 0755)
│ json
│ └─ RUN chmod +x /hello.sh
↓ 덮어쓰기
[레이어 2] 1b2c3d4e5f6a7890.../
│ layer.tar
│ └─ hello.sh (초기 권한 0644)
│ json
│ └─ COPY ./hello.sh /hello.sh
↓ 추가
[레이어 1] 0a1b2c3d4e5f6789.../
│ layer.tar
│ └─ /bin, /lib, /usr, /etc, ...
│ json
│ └─ FROM ubuntu:22.04
↓ 베이스
[최종 컨테이너 루트 파일시스템]
├─ /bin (레이어 1)
├─ /lib (레이어 1)
├─ /usr (레이어 1)
└─ /hello.sh (레이어 3, 실행 가능)
7단계: 정리
PowerShell (Windows):
# 실습 종료 후 정리 (선택)
# rm -r ./dumpimage
Bash (Linux/Mac):
# 실습 종료 후 정리 (선택)
# rm -rf ./dumpimage
실습 2: 오버레이 파일시스템 직접 체험
실습 목표
리눅스 overlayfs를 직접 마운트하여 레이어 중첩 및 Copy on Write(CoW) 동작을 확인
실습 환경
- Windows 사용자: WSL2 Ubuntu 필요
- Linux/Mac 사용자: 터미널에서 바로 실행
- 위치: 본인의 실습 디렉터리
1단계: 리눅스 환경 진입 및 디렉터리 준비
Windows (WSL2 사용):
# PowerShell에서 WSL 진입
wsl
그 후 아래 Bash 명령어 실행:
# WSL 환경에서 실행
# Windows 드라이브의 실습 디렉터리로 이동 (본인 경로로 변경)
cd /mnt/c/docker-practice
# 오버레이 실습용 디렉터리 생성
mkdir -p overlay_test
cd overlay_test
Linux/Mac:
# 실습 디렉터리로 이동 (본인 경로로 변경)
cd ~/docker-practice
# 오버레이 실습용 디렉터리 생성
mkdir -p overlay_test
cd overlay_test
2단계: 레이어 디렉터리 생성 및 파일 준비
Linux 환경 (WSL2/Linux/Mac 공통):
# 필요한 디렉터리 생성
mkdir layer1 layer2 upper work merged
# layer2 (최하위 레이어 - ubuntu:22.04 역할)
echo "layer2: base file A" > ./layer2/a.txt
echo "layer2: base file B" > ./layer2/b.txt
# layer1 (중간 레이어 - hello.sh 추가 역할)
echo "layer1: modified A" > ./layer1/a.txt
echo "layer1: hello script" > ./layer1/hello.sh
# 생성된 파일 확인 (tree 명령어 사용)
tree .
# tree가 없는 경우 ls로 확인
# ls -la layer1/ layer2/
출력 예시:
.
├── layer1
│ ├── a.txt
│ └── hello.sh
├── layer2
│ ├── a.txt
│ └── b.txt
├── merged (비어있음 - 마운트 포인트)
├── upper (비어있음 - 쓰기 레이어)
└── work (비어있음 - 커널 작업용)
레이어 구조 이해:
레이어 설계:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
layer1 (상위 레이어)
├─ a.txt → "layer1: modified A"
└─ hello.sh → "layer1: hello script"
layer2 (하위 레이어)
├─ a.txt → "layer2: base file A"
└─ b.txt → "layer2: base file B"
중첩 시 예상 결과:
├─ a.txt → layer1의 a.txt (상위 우선)
├─ hello.sh → layer1의 hello.sh
└─ b.txt → layer2의 b.txt (layer1에 없음)
3단계: 오버레이 파일시스템 마운트
Linux 환경 (WSL2/Linux/Mac 공통):
# overlay 마운트 (루트 권한 필요)
sudo mount -t overlay overlay \
-o lowerdir=layer1:layer2,upperdir=upper,workdir=work \
merged
# 마운트 확인 (Linux/WSL2)
mount | grep overlay
💡 실습 가능 환경:
- WSL2 (Windows): ✅ 정상 작동
- Linux (Native): ✅ 정상 작동
- macOS: ⚠️ overlayfs가 기본 지원되지 않음
- macOS에서는 이 실습 진행 불가
- Docker Desktop이 내부적으로 overlayfs를 사용하지만, 직접 마운트는 불가
- macOS 사용자는 실습 3으로 건너뛰기 권장
출력 예시 (WSL2/Linux):
overlay on /mnt/c/docker-practice/overlay_test/merged type overlay (rw,relatime,lowerdir=layer1:layer2,upperdir=upper,workdir=work)
명령어 구조 분석:
mount 명령어 분석:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
sudo mount -t overlay overlay -o [...] merged
│ │ │ │ │ │
│ │ │ │ │ └─ 마운트 포인트 (결과가 보이는 곳)
│ │ │ │ │
│ │ │ │ └─ 옵션: 레이어 디렉터리 지정
│ │ │ │ lowerdir: 읽기 전용 레이어 (여러 개 가능, ':' 구분)
│ │ │ │ upperdir: 읽기/쓰기 레이어
│ │ │ │ workdir: 커널 작업용 임시 디렉터리
│ │ │ │
│ │ │ └─ 장치 이름 (overlay 사용)
│ │ │
│ │ └─ 파일시스템 타입 (overlay)
│ │
│ └─ 마운트 명령어
│
└─ 루트 권한 실행
컨테이너 비유:
lowerdir = 이미지 레이어 (읽기 전용, 공유)
upperdir = 컨테이너 쓰기 레이어 (독립)
merged = 컨테이너 루트 파일시스템
4단계: 레이어 중첩 결과 확인
# 마운트 포인트 내용 확인
tree ./merged
출력:
./merged
├── a.txt
├── b.txt
└── hello.sh
# 각 파일 내용 확인
cat ./merged/a.txt
cat ./merged/b.txt
cat ./merged/hello.sh
출력:
layer1: modified A ← layer1의 a.txt (상위 우선)
layer2: base file B ← layer2의 b.txt
layer1: hello script ← layer1의 hello.sh
분석:
파일 우선순위 적용 결과:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
파일 이름 merged에서 보이는 버전 출처
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
a.txt "layer1: modified A" layer1 (상위 우선, layer2 가려짐)
hello.sh "layer1: hello script" layer1 (layer2에 없음)
b.txt "layer2: base file B" layer2 (layer1에 없음)
원칙:
→ 동일 파일명: 상위 레이어(lowerdir 앞쪽) 우선
→ 하위 레이어는 가려지지만 삭제되지 않음
→ 파일 중복 없이 투명한 병합
5단계: Copy on Write (CoW) 동작 확인
5-1. 쓰기 작업 전 upper 디렉터리 상태 확인
Linux 환경 (WSL2/Linux/Mac 공통):
# upper 디렉터리는 비어있어야 함 (아직 쓰기 작업 없음)
ls -la ./upper
출력:
total 0
drwxr-xr-x 2 user user 4096 Jan 8 10:00 .
drwxr-xr-x 7 user user 4096 Jan 8 09:58 ..
5-2. merged에서 파일 수정 (CoW 발동)
Linux 환경 (WSL2/Linux/Mac 공통):
# merged의 b.txt 수정 (원본은 layer2에 있음)
echo "Modified from merged!" > ./merged/b.txt
# 결과 확인
cat ./merged/b.txt
출력:
Modified from merged!
# upper 디렉터리 확인 (b.txt가 복사됨)
ls -la ./upper
cat ./upper/b.txt
출력:
total 4
drwxr-xr-x 2 user user 4096 Jan 8 10:05 .
drwxr-xr-x 7 user user 4096 Jan 8 09:58 ..
-rw-r--r-- 1 user user 23 Jan 8 10:05 b.txt ← 복사됨
Modified from merged! ← 변경된 내용
5-3. 원본 레이어 확인 (변경 안 됨)
Linux 환경 (WSL2/Linux/Mac 공통):
# layer2의 원본 b.txt는 변경되지 않음
cat ./layer2/b.txt
출력:
layer2: base file B ← 원본 유지
CoW 동작 분석:
CoW 메커니즘 시각화:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[수정 전]
merged/b.txt 읽기 요청
↓
layer2/b.txt 발견 (읽기 전용)
↓
"layer2: base file B" 반환
upper: (비어있음)
[수정 시도]
merged/b.txt 쓰기 요청
↓
layer2는 읽기 전용 (쓰기 불가)
↓
CoW 발동: layer2/b.txt를 upper/로 복사
↓
upper/b.txt에 새 내용 작성
upper/b.txt: "Modified from merged!"
layer2/b.txt: "layer2: base file B" (변경 없음)
[이후 읽기]
merged/b.txt 읽기 요청
↓
upper/b.txt 발견 (최우선)
↓
"Modified from merged!" 반환
5-4. 새 파일 생성
Linux 환경 (WSL2/Linux/Mac 공통):
# merged에 새 파일 생성
echo "New file in container" > ./merged/new_file.txt
# 결과 확인
cat ./merged/new_file.txt
ls ./upper
출력:
New file in container
b.txt new_file.txt ← upper에 직접 생성됨
분석:
- 하위 레이어에 없는 새 파일은 upper에 직접 생성됨
- CoW 없이 바로 upper에 작성
6단계: 레이어 공유 테스트 (새 컨테이너 시뮬레이션)
6-1. 새 마운트 포인트 생성
Linux 환경 (WSL2/Linux/Mac 공통):
# 동일한 lowerdir를 사용하는 새 컨테이너 시뮬레이션
mkdir new_upper new_work new_merged
sudo mount -t overlay overlay \
-o lowerdir=layer1:layer2,upperdir=new_upper,workdir=new_work \
new_merged
# 마운트 확인 (Linux/WSL2)
mount | grep overlay
출력:
overlay on .../merged type overlay (...)
overlay on .../new_merged type overlay (...) ← 새로운 마운트
6-2. 레이어 공유 확인
Linux 환경 (WSL2/Linux/Mac 공통):
# new_merged도 동일한 layer1, layer2를 공유함
cat ./new_merged/a.txt
cat ./new_merged/b.txt
출력:
layer1: modified A ← layer1 공유
layer2: base file B ← layer2 공유 (원본 상태)
분석:
new_merged의b.txt는 원본 (layer2)을 보여줌- 첫 번째 컨테이너(
merged)의 변경사항(upper/b.txt)은 공유되지 않음
6-3. 독립적인 변경 확인
Linux 환경 (WSL2/Linux/Mac 공통):
# new_merged에서 파일 수정
echo "New container modification" > ./new_merged/a.txt
# 각 컨테이너에서 확인
cat ./new_merged/a.txt
cat ./merged/a.txt
출력:
New container modification ← new_merged의 변경사항
layer1: modified A ← merged는 영향 없음
# upper 디렉터리 비교
ls ./upper
ls ./new_upper
출력:
b.txt new_file.txt ← 첫 번째 컨테이너
a.txt ← 두 번째 컨테이너
레이어 공유 메커니즘 시각화:
레이어 공유와 독립성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[공유 읽기 전용 레이어]
layer1/ ─┐
layer2/ ─┤
├──→ [컨테이너 1]
│ merged/
│ upper/ (b.txt, new_file.txt)
│
└──→ [컨테이너 2]
new_merged/
new_upper/ (a.txt)
특징:
→ layer1, layer2는 공유 (메모리/스토리지 절약)
→ upper는 독립 (각 컨테이너 전용)
→ 한 컨테이너의 변경이 다른 컨테이너에 영향 없음
→ 동일 이미지로 수백 개 컨테이너 실행해도 효율적
7단계: 실습 정리
Linux 환경 (WSL2/Linux/Mac 공통):
# 마운트 해제 (중요!)
sudo umount ./merged
sudo umount ./new_merged
# 마운트 해제 확인
mount | grep overlay
# (출력 없음)
# 실습 디렉터리 삭제 (선택)
cd ..
rm -rf overlay_test
Windows 사용자 (WSL 종료):
# WSL 종료
exit
그 후 PowerShell로 돌아옴
실습 3: Docker 스토리지 위치 확인 (선택)
실습 목표
실제 Docker가 레이어를 저장하는 /var/lib/docker/overlay2 디렉터리를 확인하여 실습 2와의 연결성을 이해
GraphDriver: 도커 이미지/컨테이너의 스토리지 관련 정보. 레이어 경로, 마운트 정보 등 포함.
nsenter: 다른 프로세스의 네임스페이스에 진입하는 명령어. 컨테이너/VM 내부 접근에 사용됨.
실습 환경
- Windows: WSL2 Ubuntu 필요 + Docker Desktop WSL2 통합 활성화
- Linux: 터미널에서 바로 실행
- Mac: Docker Desktop VM 내부 (접근 복잡)
- 권한: 루트 권한 필요
사전 준비: Windows 사용자 - Docker Desktop WSL2 통합 확인
Windows 사용자는 먼저 다음을 확인해주세요:
⚠️ WSL2에서 Docker 명령어가 작동하지 않는 경우:
Docker Desktop에서 WSL2 통합을 활성화해야 합니다.
설정 방법:
- Docker Desktop 실행
- 설정(Settings) → Resources → WSL Integration
- "Enable integration with my default WSL distro" 체크
- 사용 중인 Ubuntu 배포판 토글 활성화
- "Apply & Restart" 클릭
설정 후 WSL을 재시작하거나 새 터미널을 열어주세요.
또는 PowerShell에서 직접 실행하는 방법:
# PowerShell에서 직접 Docker 명령 실행 (WSL 진입 없이)
docker info | Select-String "Storage Driver" -Context 0,5
💡 Mac 사용자: Docker Desktop은 별도 VM에서 실행되므로 직접 접근이 어려움. 이 실습은 건너뛰어도 무방
1단계: Docker 스토리지 드라이버 확인
방법 1: PowerShell에서 직접 실행 (Windows, 권장):
# Docker 정보 확인 (WSL 진입 불필요)
docker info | Select-String "Storage Driver" -Context 0,5
방법 2: WSL2 환경에서 실행 (Linux):
# PowerShell에서 WSL 진입 (Windows만 해당)
wsl
Windows PowerShell:
# Docker 정보 확인
docker info | Select-String "Storage Driver" -Context 0,5
Linux/Mac (Bash) 환경:
# Docker 정보 확인
docker info | grep -A 5 "Storage Driver"
Windows (Docker Desktop) 출력 예시:
Storage Driver: overlayfs
Backing Filesystem: extfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
Linux (Native Docker) 출력 예시:
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
💡 스토리지 드라이버 차이:
- Windows Docker Desktop:
overlayfs- VM 내부에서 최적화된 스토리지 드라이버- Linux Native Docker:
overlay2- 리눅스 커널의 overlayfs 직접 사용- 둘 다 동일한 레이어 개념을 사용하지만, 구현 방식이 다름
2단계: overlay2 디렉터리 확인
⚠️ Windows + Docker Desktop 사용자 주의:
Docker Desktop을 사용하는 경우,
/var/lib/docker는 Docker Desktop의 VM 내부에 위치중요: Docker Desktop은
overlay2디렉터리를 사용하지 않음.overlayfs를 사용하며, VM 내부 확인 시/var/lib/docker아래에 다음 구조 존재:
buildkit/- BuildKit 데이터containers/- 컨테이너 정보rootfs/- 루트 파일시스템volumes/- 볼륨 데이터결론: 이 단계(overlay2 확인)는 Windows Docker Desktop에서 불가능. 바로 3단계(GraphDriver 정보 확인)로 진행
참고: VM 내부 접근 방법 (overlay2는 없지만 구조 확인 가능):
docker run -it --rm --privileged --pid=host alpine nsenter -t 1 -m -u -n -i sh ls -la /var/lib/docker/ exit
Linux 환경 (Native Docker):
# overlay2 저장소 위치 확인
sudo ls -la /var/lib/docker/overlay2 | head -n 20
출력 예시:
drwx--x--- 3 root root 4096 Jan 8 09:00 0a1b2c3d4e5f6789...
drwx--x--- 3 root root 4096 Jan 8 09:00 1b2c3d4e5f6a7890...
drwx--x--- 3 root root 4096 Jan 8 10:00 2c3d4e5f6a7b8901...
drwx--x--- 3 root root 4096 Jan 8 10:05 3d4e5f6a7b8c9012...
...
분석:
- 각 디렉터리가 하나의 레이어를 나타냄
myimage:v1의 레이어도 여기에 저장됨
3단계: myimage:v1의 레이어 찾기
PowerShell (Windows, 권장):
# myimage:v1의 GraphDriver 정보 확인
docker inspect myimage:v1 | Select-String "GraphDriver" -Context 0,15
Windows (Docker Desktop) 출력 예시:
"GraphDriver": {
"Data": null,
"Name": "overlayfs"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:73974f74b436...",
"sha256:36a90ecdccdf...",
"sha256:5f70bf18a086..."
]
}
Windows PowerShell:
# myimage:v1의 GraphDriver 정보 확인
docker inspect myimage:v1 | Select-String "GraphDriver" -Context 0,15
Linux/Mac (Bash) 환경:
# myimage:v1의 GraphDriver 정보 확인
docker inspect myimage:v1 | grep -A 15 "GraphDriver"
Linux (Native Docker) 출력 예시:
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/1b2c3d4e5f6a7890.../diff:
/var/lib/docker/overlay2/0a1b2c3d4e5f6789.../diff",
"MergedDir": "/var/lib/docker/overlay2/2c3d4e5f6a7b8901.../merged",
"UpperDir": "/var/lib/docker/overlay2/2c3d4e5f6a7b8901.../diff",
"WorkDir": "/var/lib/docker/overlay2/2c3d4e5f6a7b8901.../work"
},
"Name": "overlay2"
}
분석:
Windows (Docker Desktop):
"Data": null- Docker Desktop은 VM 내부 경로를 노출하지 않음"Name": "overlayfs"- Docker Desktop 최적화 스토리지 드라이버RootFS.Layers- 실제 레이어 SHA256 해시 확인 가능
Linux (Native Docker):
LowerDir: 읽기 전용 레이어 (여러 개, 콜론으로 구분)UpperDir: 최상위 이미지 레이어MergedDir: 레이어 중첩 결과 (컨테이너 실행 시 사용)WorkDir: 커널 작업용 디렉터리
LowerDir / UpperDir / WorkDir: LowerDir는 읽기 전용 하위 레이어들, UpperDir는 읽기/쓰기 가능한 상위 레이어, WorkDir는 커널이 내부 작업에 사용하는 디렉터리.
MergedDir: 모든 레이어가 중첩된 최종 결과. 컨테이너가 실제로 보는 파일시스템.
💡 Windows 사용자: Docker Desktop은 추상화 계층으로 인해 실제 파일 시스템 경로가 보이지 않음. 하지만
RootFS.Layers에서 각 레이어의 해시를 확인할 수 있으며, 레이어 개념 이해에는 충분
4단계: 특정 레이어 내용 확인
💡 참고: 이 단계는 Linux Native Docker 환경에서만 가능. Windows Docker Desktop 사용자는 이 단계를 건너뛰고 5단계로 진행
Linux 환경 (Native Docker만 해당):
# 레이어 디렉터리 내용 확인 (UpperDir 경로 사용)
sudo ls -la /var/lib/docker/overlay2/2c3d4e5f6a7b8901.../diff
출력 예시:
drwxr-xr-x 2 root root 4096 Jan 8 10:00 .
drwx--x--- 5 root root 4096 Jan 8 10:00 ..
-rwxr-xr-x 1 root root 65 Jan 8 10:00 hello.sh ← 레이어 내용
# hello.sh 내용 확인
sudo cat /var/lib/docker/overlay2/2c3d4e5f6a7b8901.../diff/hello.sh
출력:
#!/bin/bash
set -eu
echo "Hello, World!"
exec sleep infinity
분석:
- 실습 2의
layer1/hello.sh와 동일한 개념 - Docker는 이 디렉터리들을 overlay로 마운트하여 컨테이너 루트 FS 구성
Windows 사용자를 위한 대안:
실제 레이어 파일을 직접 볼 수는 없지만, 3단계에서 확인한 RootFS.Layers의 SHA256 해시가 각 레이어를 고유하게 식별:
- 레이어 1 (Ubuntu 베이스):
sha256:73974f74b436... - 레이어 2 (hello.sh COPY):
sha256:36a90ecdccdf... - 레이어 3 (chmod 실행):
sha256:5f70bf18a086...
5단계: 실행 중인 컨테이너의 레이어 확인
PowerShell (Windows):
# 컨테이너 실행
docker run -d --name layer_test myimage:v1
# 컨테이너의 GraphDriver 확인
docker inspect layer_test | Select-String "GraphDriver" -Context 0,15
Windows (Docker Desktop) 출력 예시:
"GraphDriver": {
"Data": null,
"Name": "overlayfs"
}
분석 (Windows):
- Docker Desktop은 컨테이너의 레이어 경로를 직접 노출하지 않음
- 하지만 개념은 동일: 이미지 레이어 위에 컨테이너 전용 쓰기 레이어가 생성됨
Linux 환경 (Native Docker):
Windows PowerShell:
# 컨테이너 실행
docker run -d --name layer_test myimage:v1
# 컨테이너의 GraphDriver 확인
docker inspect layer_test | Select-String "GraphDriver" -Context 0,15
Linux/Mac (Bash):
# 컨테이너 실행
docker run -d --name layer_test myimage:v1
# 컨테이너의 GraphDriver 확인
docker inspect layer_test | grep -A 15 "GraphDriver"
Linux 출력 예시:
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/3d4e5f6a7b8c9012.../diff:
/var/lib/docker/overlay2/2c3d4e5f6a7b8901.../diff:
/var/lib/docker/overlay2/1b2c3d4e5f6a7890.../diff:
/var/lib/docker/overlay2/0a1b2c3d4e5f6789.../diff",
"MergedDir": "/var/lib/docker/overlay2/3d4e5f6a7b8c9012.../merged",
"UpperDir": "/var/lib/docker/overlay2/3d4e5f6a7b8c9012.../diff",
"WorkDir": "/var/lib/docker/overlay2/3d4e5f6a7b8c9012.../work"
},
"Name": "overlay2"
}
분석 (Linux):
LowerDir: 이미지의 모든 레이어 (읽기 전용, 공유)UpperDir: 컨테이너 전용 읽기/쓰기 레이어 (새로 생성됨)MergedDir: 최종 루트 파일시스템 (컨테이너가 보는 뷰)
컨테이너 쓰기 레이어 테스트 (모든 OS 공통):
# PowerShell 또는
# Bash
공통 명령어:
# 컨테이너 내부에서 파일 생성
docker exec layer_test bash -c "echo 'Container data' > /test.txt"
# 컨테이너 내부에서 확인
docker exec layer_test cat /test.txt
출력:
Container data ← 컨테이너 쓰기 레이어에 저장됨
💡 핵심 개념:
- Windows와 Linux 모두에서 컨테이너는 독립적인 쓰기 레이어를 가짐
- 이미지 레이어는 읽기 전용으로 공유됨
- 컨테이너에서 파일을 생성/수정하면 쓰기 레이어에만 기록됨
- 이것이 바로 Copy-on-Write (CoW) 메커니즘
정리:
모든 OS 공통:
# 컨테이너 정리
docker stop layer_test
docker rm layer_test
실습 종합 정리
3가지 실습의 연결성
레이어 구조 이해의 흐름:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[실습 1] docker save로 이미지 분석
↓
이미지는 여러 layer.tar의 집합
각 tar는 파일시스템 변경사항 포함
↓
[실습 2] overlayfs 직접 체험
↓
여러 디렉터리를 중첩하면 하나의 FS로 보임
상위 레이어 우선, CoW로 하위 보호
↓
[실습 3] Docker 실제 저장소 확인
↓
Docker는 /var/lib/docker/overlay2에 레이어 저장
실행 시 overlayfs로 마운트
컨테이너는 독립적인 upper 레이어 생성
핵심 개념 재확인
1. 레이어는 tar 아카이브
- 실습 1:
layer.tar파일로 확인 - 각 레이어는 파일 변경사항의 스냅샷
2. overlayfs로 레이어 중첩
- 실습 2:
mount -t overlay로 직접 체험 - 여러 디렉터리를 하나로 병합
3. Copy on Write (CoW)
- 실습 2: 파일 수정 시 upper로 복사됨 확인
- 하위 레이어는 읽기 전용 유지
4. 레이어 공유
- 실습 2: 동일 lowerdir로 여러 마운트 생성
- 실습 3: 이미지 레이어는 컨테이너 간 공유
5. 독립적인 컨테이너 파일시스템
- 실습 2: 각 마운트는 독립적인 upper 보유
- 실습 3: 각 컨테이너는 독립적인 UpperDir 생성
추가 학습 자료
관련 명령어
Windows PowerShell:
# 이미지 히스토리 (레이어별 크기 확인)
docker history myimage:v1
# 이미지 상세 정보
docker inspect myimage:v1
# 컨테이너 변경사항 확인
docker diff [컨테이너명]
# 현재 사용 중인 스토리지 드라이버
docker info | Select-String "Storage Driver"
Linux/Mac (Bash):
# 이미지 히스토리 (레이어별 크기 확인)
docker history myimage:v1
# 이미지 상세 정보
docker inspect myimage:v1
# 컨테이너 변경사항 확인
docker diff [컨테이너명]
# 현재 사용 중인 스토리지 드라이버
docker info | grep "Storage Driver"
docker diff: 컨테이너에서 변경된 파일 목록 확인. A(추가), C(변경), D(삭제)로 표시됨.
docker history: 이미지의 레이어 히스토리 확인. 각 레이어의 크기, 생성 명령어 표시.
overlay2 vs overlay 차이
| 항목 | overlay | overlay2 |
|---|---|---|
| 도입 시기 | Docker 1.12 | Docker 17.06+ |
| inode 사용 | 높음 (하드링크 많음) | 낮음 (효율적) |
| 성능 | 중간 | 높음 |
| 권장 여부 | 레거시 | ✅ 권장 |
| 커널 요구사항 | 3.18+ | 4.0+ |
inode: 파일시스템에서 파일 메타데이터를 저장하는 자료구조. 파일 권한, 크기, 위치 정보 포함.
실무 팁
1. 레이어 수 최소화
# 나쁜 예
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
# 좋은 예
RUN apt-get update && apt-get install -y \
package1 \
package2 \
&& rm -rf /var/lib/apt/lists/*
2. .dockerignore 사용
- 불필요한 파일을 빌드 컨텍스트에서 제외
- 레이어 크기 감소
3. 멀티 스테이지 빌드
- 빌드 도구는 최종 이미지에서 제외
- 최종 이미지 크기 최소화
참고 자료
공식 문서:
- Docker Image Inspect: https://docs.docker.com/engine/reference/commandline/image_inspect/
- Docker History: https://docs.docker.com/engine/reference/commandline/history/
- Storage Driver: https://docs.docker.com/storage/storagedriver/
도구:
- Dive (이미지 분석 도구): https://github.com/wagoodman/dive
- Skopeo (이미지 관리 도구): https://github.com/containers/skopeo